Spring Security | Note-10

Spring Security Note-10


微信登录开发

微信开发的整个流程,原理和QQ是几乎一致;

api 定义api绑定的公共接口

config 微信的一些配置信息

connect与服务提供商建立连接所需的一些类

代码过多且雷同,详见GITHUB;


社交帐号绑定与解绑

实现绑定与解绑,需要知道社交账号的绑定状态,绑定就是重新经历OAuth2流程,关联当前登录用户;

解绑就是删除UserConnection表中的数据;

Spring Social默认在ConnectController类上已经实现了以上需求;

获取状态

/connect获取状态

ConnectController中的方法只提供了数据并没有提供视图;

实现connect/status视图即可获得社交账号的绑定状态;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@RequestMapping(method=RequestMethod.GET)
public String connectionStatus(NativeWebRequest request, Model model) {
setNoCache(request);
processFlash(request, model);
// 根据userId查询UserConnection表
Map<String, List<Connection<?>>> connections = connectionRepository.findAllConnections();
// 系统中已经注册的服务提供商
model.addAttribute("providerIds", connectionFactoryLocator.registeredProviderIds());
model.addAttribute("connectionMap", connections);
// 返回connectView()
return connectView();
}
protected String connectView() {
// connect/status
return getViewPath() + "status";
}
实现connect/status
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Component("connect/status")
public class ImoocConnectionStatusView extends AbstractView {
@Autowired
private ObjectMapper objectMapper;

@Override
protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, List<Connection<?>>> connections = (Map<String, List<Connection<?>>>) model.get("connectionMap");
Map<String, Boolean> result = new HashMap<>();
for (String key : connections.keySet()) {
result.put(key, CollectionUtils.isNotEmpty(connections.get(key)));
}
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(result));
}
}

绑定的实现

ConnectController中的方法 /connect/{providerId} 绑定社交帐号

1
2
3
4
5
6
7
8
9
10
11
12
13
////跳转到授权的页面
@RequestMapping(value="/{providerId}", method=RequestMethod.POST)
public RedirectView connect(@PathVariable String providerId, NativeWebRequest request) {
ConnectionFactory<?> connectionFactory = connectionFactoryLocator.getConnectionFactory(providerId);
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<String, String>();
preConnect(connectionFactory, parameters, request);
try {
return new RedirectView(connectSupport.buildOAuthUrl(connectionFactory, request, parameters));
} catch (Exception e) {
sessionStrategy.setAttribute(request, PROVIDER_ERROR_ATTRIBUTE, e);
return connectionStatusRedirect(providerId, request);
}
}
授权成功的回调地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 将当前的登录账户与社交账号绑定(写入到UserConnection表)
@RequestMapping(value="/{providerId}", method=RequestMethod.GET, params="code")
public RedirectView oauth2Callback(@PathVariable String providerId, NativeWebRequest request) {
try {
OAuth2ConnectionFactory<?> connectionFactory = (OAuth2ConnectionFactory<?>) connectionFactoryLocator.getConnectionFactory(providerId);
Connection<?> connection = connectSupport.completeConnection(connectionFactory, request);
addConnection(connection, connectionFactory, request);
} catch (Exception e) {
sessionStrategy.setAttribute(request, PROVIDER_ERROR_ATTRIBUTE, e);
logger.warn("Exception while handling OAuth2 callback (" + e.getMessage() + "). Redirecting to " + providerId +" connection status page.");
}
return connectionStatusRedirect(providerId, request);
}

// 返回/connext/qqed视图
protected RedirectView connectionStatusRedirect(String providerId, NativeWebRequest request) {
HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
String path = "/connect/" + providerId + getPathExtension(servletRequest);
if (prependServletPath(servletRequest)) {
path = servletRequest.getServletPath() + path;
}
return new RedirectView(path, true);
}
绑定结果的视图
1
2
3
4
5
6
7
8
9
10
11
public class ImoocConnectView extends AbstractView {
@Override
protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
response.setContentType("text/html;charset=UTF-8");
if (model.get("connections") == null) {
response.getWriter().write("<h3>解绑成功</h3>");
} else {
response.getWriter().write("<h3>绑定成功</h3>");
}
}
}
注入绑定结果的视图
1
2
3
4
5
6
7
8
9
@Configuration
@ConditionalOnProperty(prefix = "imooc.security.social.weixin", name = "app-id")
public class WeixinAutoConfiguration extends SocialAutoConfigurerAdapter {
@Bean("connect/weixinConnected")
@ConditionalOnMissingBean(name = "weixinConnectedView")
public View weixinConnectedView(){
return new ImoocConnectView();
}
}

解除绑定的实现

解除绑定的操作其实与绑定是一样的,只是发出请求的方式是DELETE,而不是POST;

1
2
http://localhost/connect/weixin
Method:DELETE

发送请求后,虽然返回的响应结果是302,但是实际上在数据库中,已经完成了记录的删除;

补充一下解绑的视图;

1
2
3
4
5
@Bean({"connect/weixinConnect","connect/weixinConnected"})
@ConditionalOnMissingBean(name = "weixinConnectedView")
public View weixinConnectedView(){
return new ImoocConnectView();
}